import math
import random


def get_random_number(a, b):
    return (b - a) * random.random() + a


def get_matrix(m, n, fill=0.0):
    a = []
    for i in range(m):
        a.append([fill] * n)
    return a


def sigmoid(x):
    return math.tanh(x)


# 函数sigmoid的派生函数
def derived_sigmoid(x):
    return 1.0 - x ** 2


# 构造三层BP网络架构
class BP_there_layers:
    def __init__(self, num_in, num_hidden, num_out):

        self.num_in = num_in + 1  # 增加一个偏置结点
        self.num_hidden = num_hidden + 1  # 增加一个偏置结点
        self.num_out = num_out
        self.active_in = [1.0] * self.num_in
        self.active_hidden = [1.0] * self.num_hidden
        self.active_out = [1.0] * self.num_out
        self.wight_in = get_matrix(self.num_in, self.num_hidden)
        self.wight_out = get_matrix(self.num_hidden, self.num_out)
        # 对权值矩阵初始化
        for i in range(self.num_in):
            for j in range(self.num_hidden):
                self.wight_in[i][j] = get_random_number(-0.2, 0.2)
        for i in range(self.num_hidden):
            for j in range(self.num_out):
                self.wight_out[i][j] = get_random_number(-0.2, 0.2)
        # 最后建立动量因子（矩阵）
        self.ci = get_matrix(self.num_in, self.num_hidden)
        self.co = get_matrix(self.num_hidden, self.num_out)

    def update(self, inputs):
        """进行更新各个权重参数"""
        if len(inputs) != self.num_in - 1:
            raise ValueError('与输入层节点数不符')
        # 数据输入输入层
        for i in range(self.num_in - 1):

            self.active_in[i] = inputs[i]  # active_in[]是输入数据的矩阵
        # 数据在隐藏层的处理
        for i in range(self.num_hidden - 1):
            sum = 0.0
            for j in range(self.num_in):
                sum = sum + self.active_in[i] * self.wight_in[j][i]
            self.active_hidden[i] = sigmoid(sum)  # active_hidden[]是处理完输入数据之后存储，作为输出层的输入数据
        # 数据在输出层的处理
        for i in range(self.num_out):
            sum = 0.0
            for j in range(self.num_hidden):
                sum = sum + self.active_hidden[j] * self.wight_out[j][i]
            self.active_out[i] = sigmoid(sum)  # 与上同理
        return self.active_out[:]

    def back_propagate(self, targets, lr, m):  # lr是学习率， m是动量因子
        """反向传播用于更新权重参数"""
        if len(targets) != self.num_out:
            raise ValueError('与输出层节点数不符！')
        # 首先计算输出层的误差
        out_deltas = [0.0] * self.num_out
        for i in range(self.num_out):
            error = targets[i] - self.active_out[i]
            out_deltas[i] = derived_sigmoid(self.active_out[i]) * error
        # 然后计算隐藏层误差
        hidden_deltas = [0.0] * self.num_hidden
        for i in range(self.num_hidden):
            error = 0.0
            for j in range(self.num_out):
                error = error + out_deltas[j] * self.wight_out[i][j]
            hidden_deltas[i] = derived_sigmoid(self.active_hidden[i]) * error
        # 更新输出层权值
        for i in range(self.num_hidden):

            for j in range(self.num_out):
                change = out_deltas[j] * self.active_hidden[i]

                self.wight_out[i][j] = self.wight_out[i][j] + lr * change + m * self.co[i][j]

                self.co[i][j] = change
        # 更新输入层权值
        for i in range(self.num_in):
            for i in range(self.num_hidden):
                change = hidden_deltas[j] * self.active_in[i]
                self.wight_in[i][j] = self.wight_in[i][j] + lr * change + m * self.ci[i][j]
                self.ci[i][j] = change
        # 计算总误差
        error = 0.0
        for i in range(len(targets)):
            error = error + 0.5 * (targets[i] - self.active_out[i]) ** 2
        return error

    def validate(self, patterns):
        """验证"""
        for i in patterns:
            print(i[0], '->', self.update(i[0]))

    def weights(self):
        print("打印权重矩阵")
        for i in range(self.num_in):
            print(self.wight_in[i])
        for i in range(self.num_hidden):
            print(self.wight_out[i])

    def train(self, pattern, itera=100000, lr=0.1, m=0.1):
        for i in range(itera):
            error = 0.0
            for j in pattern:
                inputs = j[0]
                targets = j[1]
                self.update(inputs)
                error = error + self.back_propagate(targets, lr, m)
            if i % 100 == 0:
                print('误差 %-.5f' % error)


def get_data():
    data = [
        [[2, 1, 4], [0]],
        [[2, 4, 5], [1]],
        [[2, 5, 1], [1]],
        [[1, 5, 1], [0]],
        [[3, 5, 6], [1]],
        [[4, 3, 1], [0]],
        [[2, 5, 2], [1]],
        [[1, 4, 1], [0]],
        [[3, 5, 1], [1]],
        [[1, 8, 4], [1]]]
    return data


def instance_run():
    """创建一个实例并计算出结果"""
    input_data = get_data()
    # 创建神经网络，3个输入节点，3个隐藏层节点，1个输出层节点
    n = BP_there_layers(3, 3, 1)
    # 训练神经网络
    n.train(input_data)
    # 测试神经网络
    n.validate(input_data)
    # 查阅权重值
    n.weights()


if __name__ == '__main__':
    instance_run()
